package com.qiusuo0226.platform.handler;

import cn.hutool.core.convert.Convert;
import com.alibaba.fastjson2.JSONObject;
import com.qiusuo0226.common.util.ConvertTool;
import com.qiusuo0226.http.util.HttpServletRequestTool;
import com.qiusuo0226.mvc.util.JsonResultModel;
import com.qiusuo0226.security.config.SecurityProperty;
import com.qiusuo0226.security.util.AesTool;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 接口请求异常统一处理
 * - @ControllerAdvice 注解表示我们定义的是一个控制器增强类
 * - @ExceptionHandler 当其他任何控制器发生异常且异常类型符合该注解中指定的异常类时，原请求将会被拦截到这个我们自定义的控制器方法中，只有一个value参数，为指定的异常类
 * @author qiusuo
 * @date 2023/6/18 16:12
 */
@RestControllerAdvice
@Slf4j
public class RequestExceptionHandler {

    @Autowired
    private SecurityProperty property;

    /**
     * 校验参数异常处理：可能1
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public JsonResultModel methodArgumentNotValidExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
        // 获取参数校验的异常对象集合，多个入参有问题就会有多个FieldError
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        requestRecordLog(request, e, "参数绑定校验异常");
        // 获取第一个异常对象的问题描述说明
        String errorMessage = fieldErrors.get(0).getDefaultMessage();
        // 实际返回前端值
        return JsonResultModel.paramsError(errorMessage);
    }

    /**
     * 校验参数异常处理：可能2
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public JsonResultModel constraintViolationExceptionHandler(HttpServletRequest request, ConstraintViolationException e) {
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        requestRecordLog(request, e, "参数绑定校验异常");
        // 获取问题描述说明
        String errorMessage = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
        // 实际返回前端值
        return JsonResultModel.paramsError(errorMessage);
    }

    /**
     * 校验参数异常处理：可能3
     */
    @ExceptionHandler(BindException.class)
    public JsonResultModel bindExceptionHandler(HttpServletRequest request, BindException e) {
        // 获取参数校验的异常对象集合，多个入参有问题就会有多个FieldError
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        requestRecordLog(request, e, "参数绑定校验异常");
        // 获取第一个异常对象的问题描述说明
        String errorMessage = fieldErrors.get(0).getDefaultMessage();
        // 实际返回前端值
        return JsonResultModel.paramsError(errorMessage);
    }


    /**
     * TODO 空指针异常处理
     */
    @ExceptionHandler(NullPointerException.class)
    public JsonResultModel nullPointerExceptionHandler(HttpServletRequest request, Exception e) {
        requestRecordLog(request, e, "空指针异常");
        return JsonResultModel.exception(e.toString() + ":" + e.getMessage());
    }

    /**
     * TODO 索引越界异常处理
     */
    @ExceptionHandler(IndexOutOfBoundsException.class)
    public JsonResultModel indexOutOfBoundsExceptionHandler(HttpServletRequest request, Exception e) {
        requestRecordLog(request, e, "数组越界");
        return JsonResultModel.exception(e.toString() + ":" + e.getMessage());
    }

    /**
     * TODO 索引越界异常处理
     */
    @ExceptionHandler(Exception.class)
    public JsonResultModel exceptionHandler(HttpServletRequest request, Exception e) {
        requestRecordLog(request, e, "");
        return JsonResultModel.exception(e.toString() + ":" + e.getMessage());
    }


    /**
     * request记录日志
     * @param request
     * @param e
     */
    private void requestRecordLog(HttpServletRequest request, Exception e, String msg) {
        // 请求request记录
        try {
            // 组成该次请求的基本信息
            String method = HttpServletRequestTool.getMethod(request);
            String remoteHost = HttpServletRequestTool.getRemoteHost(request);
            String requestURL = HttpServletRequestTool.getRequestURL(request);
            String contentType = HttpServletRequestTool.getContentType(request);
            String queryString = request.getQueryString();

            JSONObject requestJSON = new JSONObject();
            requestJSON.put("querys", queryString);
            requestJSON.put("remoteHost", remoteHost);
            requestJSON.put("requestMethod", method);
            requestJSON.put("requestURL", requestURL);
            requestJSON.put("contentType", contentType);


            if (StringUtils.equals(contentType, MediaType.APPLICATION_FORM_URLENCODED_VALUE) ||
                    StringUtils.startsWith(contentType, MediaType.MULTIPART_FORM_DATA_VALUE)) {
                Enumeration<String> parameterNames = request.getParameterNames();
                JSONObject bodys = new JSONObject();
                while (parameterNames.hasMoreElements()) {
                    String paramName = parameterNames.nextElement();
                    String[] parameter = request.getParameterValues(paramName);
                    if (null == parameter || parameter.length == 0) {
                        bodys.put(paramName, null);
                    } else if (parameter.length == 1) {
                        bodys.put(paramName, parameter[0]);
                    } else {
                        bodys.put(paramName, parameter);
                    }
                }
                requestJSON.put("bodys", bodys);
            } else {
                // 其他通用类型获取请求参数
                // @RequestBody、 request.getInputStream()、request.getReader()这三种方法是有冲突的，因为流只能被读一次。
                int len = request.getContentLength();
                ServletInputStream iii = request.getInputStream();
                byte[] buffer = new byte[len];
                iii.read(buffer, 0, len);
                String bodys = new String(buffer, Consts.UTF_8);
                requestJSON.put("bodys", bodys);
            }

            byte[] encryptBytes = AesTool.encryptECB(requestJSON.toJSONString(), ConvertTool.hexToBytes(property.getAesKey()));
            log.info("请求方式【{}】 请求类型【{}】", method, contentType);
            log.info("请求信息加密【{}】", Convert.toHex(encryptBytes));
        } catch (Exception exception) {
            log.error("Controller异常处理，request结果加密异常:", exception);
        }

        log.error(StringUtils.isBlank(msg) ? "请求异常" : msg, e);
    }
}
